rewrite-testing-frameworks icon indicating copy to clipboard operation
rewrite-testing-frameworks copied to clipboard

Assertions are failing with misleading exception even migration is executed properly

Open tkupka opened this issue 10 months ago • 0 comments

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 ...


tkupka avatar Apr 12 '24 14:04 tkupka