Skip to content

Commit 0d7b6a3

Browse files
committed
Adding TestRuleToTestInfo recipe.
Initial effort follow up effort is expected.
1 parent 1125324 commit 0d7b6a3

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.junit5;
17+
18+
import org.openrewrite.ExecutionContext;
19+
import org.openrewrite.Parser;
20+
import org.openrewrite.Recipe;
21+
import org.openrewrite.TreeVisitor;
22+
import org.openrewrite.internal.lang.Nullable;
23+
import org.openrewrite.java.*;
24+
import org.openrewrite.java.search.UsesType;
25+
import org.openrewrite.java.tree.J;
26+
import org.openrewrite.java.tree.Space;
27+
import org.openrewrite.java.tree.TypeUtils;
28+
29+
import java.util.ArrayList;
30+
import java.util.stream.Collectors;
31+
import java.util.stream.Stream;
32+
33+
public class TestRuleToTestInfo extends Recipe {
34+
35+
private static final String testNameType = "org.junit.rules.TestName";
36+
private static final MethodMatcher TEST_NAME_GET_NAME = new MethodMatcher(testNameType + " getMethodName()");
37+
38+
private static final ThreadLocal<JavaParser> TEST_INFO_PARSER = ThreadLocal.withInitial(() ->
39+
JavaParser.fromJavaVersion().dependsOn(
40+
Stream.of(
41+
Parser.Input.fromString(
42+
"package org.junit.jupiter.api;\n" +
43+
"import java.lang.reflect.Method;\n" +
44+
"import java.util.Optional;\n" +
45+
"import java.util.Set;\n" +
46+
"public interface TestInfo {\n" +
47+
" String getDisplayName();\n" +
48+
" Set<String> getTags();\n" +
49+
" Optional<Class<?>> getTestClass();\n" +
50+
" Optional<Method> getTestMethod();" +
51+
"}")).collect(Collectors.toList())
52+
).build());
53+
54+
@Override
55+
public String getDisplayName() {
56+
return "JUnit TestName @Rule to JUnit Jupiter TestInfo";
57+
}
58+
59+
@Override
60+
public String getDescription() {
61+
return "Replace usages of JUnit 4's `@Rule TestName` with JUnit 5's TestInfo.";
62+
}
63+
64+
@Override
65+
protected @Nullable TreeVisitor<?, ExecutionContext> getApplicableTest() {
66+
return new UsesType<>(testNameType);
67+
}
68+
69+
@Override
70+
protected @Nullable TreeVisitor<?, ExecutionContext> getSingleSourceApplicableTest() {
71+
return new UsesType<>(testNameType);
72+
}
73+
74+
@Override
75+
protected JavaIsoVisitor<ExecutionContext> getVisitor() {
76+
return new JavaIsoVisitor<ExecutionContext>() {
77+
@Override
78+
public J.CompilationUnit visitCompilationUnit(J.CompilationUnit cu, ExecutionContext executionContext) {
79+
J.CompilationUnit compilationUnit = super.visitCompilationUnit(cu, executionContext);
80+
maybeRemoveImport("org.junit.Rule");
81+
maybeRemoveImport(testNameType);
82+
maybeAddImport("org.junit.jupiter.api.TestInfo");
83+
doAfterVisit(new JavaVisitor<ExecutionContext>() {
84+
@Override
85+
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext executionContext) {
86+
J.MethodInvocation mi = (J.MethodInvocation) super.visitMethodInvocation(method, executionContext);
87+
if (TEST_NAME_GET_NAME.matches(mi) && mi.getSelect() != null) {
88+
return mi.getSelect().withPrefix(Space.format(" "));
89+
}
90+
return mi;
91+
}
92+
});
93+
doAfterVisit(new ChangeType(testNameType, "String"));
94+
return compilationUnit;
95+
}
96+
97+
@Override
98+
public J.Import visitImport(J.Import _import, ExecutionContext executionContext) {
99+
J.Import imp = super.visitImport(_import, executionContext);
100+
if (imp.getTypeName().equals(testNameType)) {
101+
//noinspection ConstantConditions
102+
return null;
103+
}
104+
return imp;
105+
}
106+
107+
@Override
108+
public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext executionContext) {
109+
J.VariableDeclarations varDecls = super.visitVariableDeclarations(multiVariable, executionContext);
110+
if (varDecls.getType() != null && TypeUtils.isOfClassType(varDecls.getType(), testNameType)) {
111+
varDecls = varDecls.withLeadingAnnotations(new ArrayList<>());
112+
//noinspection ConstantConditions
113+
doAfterVisit(new AddBeforeEachMethod(varDecls, getCursor().firstEnclosing(J.ClassDeclaration.class)));
114+
}
115+
return varDecls;
116+
}
117+
118+
@Override
119+
public J.NewClass visitNewClass(J.NewClass newClass, ExecutionContext executionContext) {
120+
J.NewClass nc = super.visitNewClass(newClass, executionContext);
121+
if (TypeUtils.isOfClassType(nc.getType(), testNameType)) {
122+
//noinspection ConstantConditions
123+
return null;
124+
}
125+
return nc;
126+
}
127+
128+
@Override
129+
public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext executionContext) {
130+
J.MethodDeclaration md = super.visitMethodDeclaration(method, executionContext);
131+
if (md.getLeadingAnnotations().stream().anyMatch(this::isBeforeAnnotation)) {
132+
//noinspection ConstantConditions
133+
getCursor().putMessage("before-method", getCursor().firstEnclosing(J.ClassDeclaration.class));
134+
}
135+
return md;
136+
}
137+
138+
private boolean isBeforeAnnotation(J.Annotation annotation) {
139+
return TypeUtils.isOfClassType(annotation.getType(), "org.junit.Before") || TypeUtils.isOfClassType(annotation.getType(), "org.junit.jupiter.api.BeforeEach");
140+
}
141+
142+
143+
};
144+
}
145+
146+
private static class AddBeforeEachMethod extends JavaIsoVisitor<ExecutionContext> {
147+
private final J.VariableDeclarations varDecls;
148+
private final J.ClassDeclaration enclosingClass;
149+
150+
public AddBeforeEachMethod(J.VariableDeclarations varDecls, J.ClassDeclaration enclosingClass) {
151+
this.varDecls = varDecls;
152+
this.enclosingClass = enclosingClass;
153+
}
154+
155+
@Override
156+
public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext executionContext) {
157+
J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, executionContext);
158+
if (getCursor().isScopeInPath(enclosingClass)) {
159+
String t = "@BeforeEach\n" +
160+
"public void setup(TestInfo testInfo) {\n" +
161+
" Optional<Method> testMethod = testInfo.getTestMethod();\n" +
162+
" if (testMethod.isPresent()) {\n" +
163+
" this.#{} = testMethod.get().getName();\n" +
164+
" }\n" +
165+
"}";
166+
cd = cd.withTemplate(JavaTemplate.builder(this::getCursor, t).javaParser(TEST_INFO_PARSER::get)
167+
.imports("org.junit.jupiter.api.TestInfo", "java.util.Optional", "java.lang.reflect.Method")
168+
.build(),
169+
cd.getBody().getCoordinates().lastStatement(),
170+
varDecls.getVariables().get(0).getName().getSimpleName());
171+
}
172+
return cd;
173+
}
174+
}
175+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright 2020 the original author or authors.
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.java.testing.junit5
17+
18+
import org.junit.jupiter.api.Test
19+
import org.openrewrite.Recipe
20+
import org.openrewrite.java.JavaParser
21+
import org.openrewrite.java.JavaRecipeTest
22+
23+
class TestRuleToTestInfoTest : JavaRecipeTest {
24+
override val parser: JavaParser = JavaParser.fromJavaVersion()
25+
.classpath("junit")
26+
.build()
27+
override val recipe: Recipe
28+
get() = TestRuleToTestInfo()
29+
@Test
30+
fun testRuleToTestInfo() = assertChanged(
31+
before = """
32+
import org.junit.Rule;
33+
import org.junit.rules.TestName;
34+
public class SomeTest {
35+
@Rule
36+
public TestName name = new TestName();
37+
protected String randomName() {
38+
return name.getMethodName();
39+
}
40+
}
41+
""",
42+
after = """
43+
import org.junit.jupiter.api.TestInfo;
44+
45+
public class SomeTest {
46+
47+
public String name;
48+
protected String randomName() {
49+
return name;
50+
}
51+
52+
@BeforeEach
53+
public void setup(TestInfo testInfo) {
54+
Optional<Method> testMethod = testInfo.getTestMethod();
55+
if (testMethod.isPresent()) {
56+
this.name = testMethod.get().getName();
57+
}
58+
}
59+
}
60+
"""
61+
)
62+
}

0 commit comments

Comments
 (0)