rewrite-testing-frameworks
rewrite-testing-frameworks copied to clipboard
Assertions are failing with misleading exception even migration is executed properly
What version of OpenRewrite are you using?
I am using rewrite-recipe-bom:2.9.0
How are you running OpenRewrite?
I am using the Gradle plugin, and my project is a multi module project.
plugins {
id 'java-library'
}
dependencies {
implementation platform(libs.openRewriteBom)
implementation("org.openrewrite:rewrite-java")
runtimeOnly("org.openrewrite:rewrite-java-17")
implementation("org.openrewrite:rewrite-maven")
implementation("org.openrewrite:rewrite-yaml")
implementation("org.openrewrite:rewrite-properties")
implementation("org.openrewrite:rewrite-xml")
testImplementation("org.openrewrite:rewrite-test")
testImplementation libs.bundles.tests
testImplementation libs.slf
}
tasks.compileJava {
options.release.set(17)
}
What is the smallest, simplest way to reproduce the problem?
I wrote a simple recipe that should add a chain method to the existing one. The use case is that we have changed the return type from String
to an Object and want to migrate a code.
String result = testService.testMethod(data)
to
String result = testService.testMethod(data).getData()
The recipe is failing only during test assertions. when I execute it it migrates the code properly.
Create a class in src/main/java
package com.test;
public class TestService {
public String testMethod(String data) {
return data;
}
}
then recipe:
package com.xyz.rewrite;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import org.openrewrite.ExecutionContext;
import org.openrewrite.Option;
import org.openrewrite.Preconditions;
import org.openrewrite.Recipe;
import org.openrewrite.TreeVisitor;
import org.openrewrite.java.JavaTemplate;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.MethodMatcher;
import org.openrewrite.java.search.UsesMethod;
import org.openrewrite.java.tree.J;
public class ReplaceMethodCall extends Recipe {
@Option(displayName = "Existing Method pattern",
description = "A method patterns that are called",
example = "")
String existingMethodPattern;
@Option(displayName = "Existing Method pattern",
description = "A method patterns that are called",
example = "")
String newMethodTemplate;
@Option(displayName = "Parameter indexes referenced from method template",
description = "When the new method template reference parameters here you define it's indexes",
example = "[1, 2]")
List<Integer> parameterIndexes;
public ReplaceMethodCall() {
}
public ReplaceMethodCall(String existingMethodPattern, String newMethodTemplate, List<Integer> parameterIndexes) {
this.existingMethodPattern = existingMethodPattern;
this.newMethodTemplate = newMethodTemplate;
this.parameterIndexes = parameterIndexes;
}
@Override
public String getDisplayName() {
return "Replace method call with new one";
}
@Override
public String getDescription() {
return "Replace method with new call chain. 1-st parameter is the selector others are configurable.";
}
@Override
public TreeVisitor<?, ExecutionContext> getVisitor() {
MethodMatcher methodToMatch = new MethodMatcher(existingMethodPattern);
return Preconditions.check(new UsesMethod<>(methodToMatch), new JavaVisitor<ExecutionContext>() {
private final JavaTemplate template = JavaTemplate
.builder(newMethodTemplate)
.build();
@Override
public J visitMethodInvocation(J.MethodInvocation method, ExecutionContext ctx) {
final J.MethodInvocation result = (J.MethodInvocation) super.visitMethodInvocation(method, ctx);
if (methodToMatch.matches(method)) {
List<Object> parameters = new LinkedList<>();
parameters.add(result.getSelect());
Optional.ofNullable(parameterIndexes).orElse(Collections.emptyList())
.forEach(index -> parameters.add(result.getArguments().get(index)));
return template.apply(updateCursor(result), result.getCoordinates().replace(), parameters.toArray());
}
return result;
}
});
}
}
Then test:
package com.xyz.rewrite;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.openrewrite.config.Environment;
import org.openrewrite.java.Assertions;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
public class ReplaceJavaMethodCallTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec
.parser(JavaParser
.fromJavaVersion()
.classpath("MODULE_NAME")
.logCompilationWarningsAndErrors(true))
.recipe(Environment.builder()
.scanRuntimeClasspath("com.test")
.build()
.activateAll())
.recipe(new ReplaceMethodCall(
"com.test.TestService testMethod(java.lang.String)",
"#{any(com.test.TestService)}.testMethod(#{any(java.lang.String)}).getData()",
Arrays.asList(Integer.valueOf(0))));
}
@Test
void checkJavaMethodChainReplacement() {
rewriteRun(
Assertions.java(
// The Java source file before the recipe is run:
"""
import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data);
}
}
""",
// The expected Java source file after the recipe is run:
"""
import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data).getData();
}
}
"""));
}
}
What did you expect to see?
Test would pass
What did you see instead?
Test is failing with weird exception.
What is the full stack trace of any errors you encountered?
java.lang.IllegalStateException: LST contains missing or invalid type information
MethodInvocation->MethodInvocation->Return->Block->MethodDeclaration->Block->ClassDeclaration->CompilationUnit
/*~~(MethodInvocation type is missing or malformed)~~>*/testService.testMethod(data)
at org.openrewrite.java.Assertions.assertValidTypes(Assertions.java:87)
at org.openrewrite.java.Assertions.validateTypes(Assertions.java:57)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:509)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:133)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:128)
at com.......ReplaceJavaMethodCallTest.checkJavaMethodChainReplacement(ReplaceJavaMethodCallTest.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
Different expected code
When the expected code is changed to:
package com.xyz.rewrite;
import java.util.Arrays;
import org.junit.jupiter.api.Test;
import org.openrewrite.config.Environment;
import org.openrewrite.java.Assertions;
import org.openrewrite.java.JavaParser;
import org.openrewrite.test.RecipeSpec;
import org.openrewrite.test.RewriteTest;
public class ReplaceJavaMethodCallTest implements RewriteTest {
@Override
public void defaults(RecipeSpec spec) {
spec
.parser(JavaParser
.fromJavaVersion()
.classpath("MODULE_NAME")
.logCompilationWarningsAndErrors(true))
.recipe(Environment.builder()
.scanRuntimeClasspath("com.test")
.build()
.activateAll())
.recipe(new ReplaceMethodCall(
"com.test.TestService testMethod(java.lang.String)",
"#{any(com.test.TestService)}.testMethod(#{any(java.lang.String)}).getData()",
Arrays.asList(Integer.valueOf(0))));
}
@Test
void checkJavaMethodChainReplacement() {
rewriteRun(
Assertions.java(
// The Java source file before the recipe is run:
"""
import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data);
}
}
""",
// The expected Java source file after the recipe is run:
"""
import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data).getData().nothing();
}
}
"""));
}
}
The test is failing with reasonable exception:
org.opentest4j.AssertionFailedError: [Unexpected result in "TestConsumer.java":
diff --git a/TestConsumer.java b/TestConsumer.java
index 9e98775..09a1cb5 100644
--- a/TestConsumer.java
+++ b/TestConsumer.java
@@ -6,7 +6,7 @@
public String aMethod() {
String data = "nothing";
- return testService.testMethod(data).getData().nothing();
+ return testService.testMethod(data).getData();
}
}
]
expected: "import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data).getData().nothing();
}
}
"
but was: "import com.test.TestService;
public class TestConsumer {
private TestService testService;
public String aMethod() {
String data = "nothing";
return testService.testMethod(data).getData();
}
}
"
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:499)
at org.openrewrite.test.RewriteTest.assertContentEquals(RewriteTest.java:617)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:508)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:133)
at org.openrewrite.test.RewriteTest.rewriteRun(RewriteTest.java:128)
at ...